﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RayDen.Library.Components.Surface;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Scene;

namespace RayDen.RayEngine.AdvancedMaterials
{
    internal class AdvancedSurfaceMaterial : ISurfaceMaterial
    {
        public static bool Has(BrdfType val, BrdfType f)
        {
            //return val.HasFlag(f);
            return ((uint)val & (uint)f).Equals((uint)f);
        }
        public BrdfClass Class { get; set; }

        public const int MaxBrdf = 4;
        public string Name { get; set; }
        public BrdfType Type { get; internal set; }

        public TextureInfo alphaTexture;

        public TextureInfo AlphaTexture { get { return this.alphaTexture; } }

        public bool TransparentShadows { get; set; }

        public bool IsDiffuse()
        {
            return Has( Type, BrdfType.Diffuse );
        }

        public float ContinuationProbability { get { return this.First.ContinuationProb; } }

        public float Pdf(ref Vector wo, ref Vector wi)
        {
            return this.First.Pdf(ref wo, ref wi, BxDFTypes.BSDF_ALL);
        }

        public bool IsSpecular()
        {
            return Has(Type, BrdfType.Specular) || Has(Type, BrdfType.Glossy);
        }

        public bool IsRefractive()
        {
            return Has(Type, BrdfType.Refractive); ;
        }

        public ElementaryBrdf First
        {
            get { return this.brdfs[0]; }
        }

        protected ElementaryBrdf[] brdfs;
        protected float[] brdfWeights;

        [NonSerialized]
        protected float defaultWeight;

        public AdvancedSurfaceMaterial()
        {
            brdfs = new ElementaryBrdf[MaxBrdf];
            brdfWeights = new float[MaxBrdf];
            defaultWeight = 1f / MaxBrdf;
        }

        public AdvancedSurfaceMaterial(ElementaryBrdf brdf)
        {
            brdfs = new[] { brdf };
            brdfWeights = new float[MaxBrdf];
            defaultWeight = 1f;
        }

        public AdvancedSurfaceMaterial(params ElementaryBrdf[] brdfs)
        {
            this.brdfs = brdfs;
            defaultWeight = 1f / (float)brdfs.Length;
            brdfWeights = brdfs.Select(i => defaultWeight).ToArray();
        }

        public void Add(ElementaryBrdf brdf)
        {
            for (int i = 0; i < MaxBrdf; i++)
            {
                if (brdfs[i] == null)
                {
                    brdfs[i] = brdf;
                    defaultWeight = 1f / (float)brdfs.Count(b => b != null);
                    brdfWeights =brdfs.Where(b => b != null).Select(b =>defaultWeight).ToArray();
                    return;
                }
            }
            this.brdfs = brdfs.Union(new[] { brdf }).ToArray();
            defaultWeight = 1f / (float)brdfs.Count(b => b != null);
            brdfWeights = brdfs.Where(b => b != null).Select(b => defaultWeight).ToArray();
        }

        public ElementaryBrdf Sample(float u0)
        {
            var rnd = Math.Round(u0 * (brdfs.Length - 1));
            return this.brdfs[(int)rnd];
        }

        public void f(ref Vector lwo, ref Vector lwi, ref Normal N, ref RgbSpectrum in_fs, out RgbSpectrum fs, BrdfType types = BrdfType.Diffuse)
        {
            this.CreateFrame(ref N);
            var wo = this.WorldToLocal(ref lwo);
            var wi = this.WorldToLocal(ref lwi);
            if (brdfs.Length == 1)
            {
                if (brdfs[0].IsSpecular())
                {
                    fs = new RgbSpectrum(0f);
                }
                else
                    brdfs[0].f(ref wo, ref wi, ref N, out fs);
                return;
            }

            var res = new RgbSpectrum();
            fs = new RgbSpectrum();
            foreach (var surfaceBsdf in brdfs)
            {
                if (surfaceBsdf.Type.HasFlag(types))
                {
                    surfaceBsdf.f(ref wo, ref wi, ref N, out res);
                    fs += res;
                }
            }
        }

        public RgbSpectrum Sample_f(ref Vector lwo, out Vector wi, ref Normal N, ref Normal shadeN, ref RgbSpectrum in_fs, float u0, float u1, float u2, ref SurfaceTextureData surfaceData, out float pdf, out BsdfEvent bsdfEvent)
        {
            this.CreateFrame(ref N);
            var wo = this.WorldToLocal(ref lwo);
            var res = new RgbSpectrum();
            pdf = 0f;
            bsdfEvent = BsdfEvent.None;
            wi = new Vector();
            ElementaryBrdf brdf;
            if (this.brdfs.Length == 1)
            {
                brdf = this.brdfs[0];
            }
            else
                brdf = Sample(u2);

            if (brdf != null)
            {
                BsdfSampleData result;
                brdf.Sample_f(ref wo, ref N, ref shadeN, ref surfaceData, u0, u1, u2, out result);
                res = result.F;
                pdf = result.Pdf ; //*(1f / brdfs.Length);
                wi = LocalToWorld(ref result.Wi);
                bsdfEvent = brdf.Type.TypeToEvent();
            }

            return res;
        }
     

        public IEnumerator<ElementaryBrdf> GetEnumerator()
        {
            return brdfs.ToList().GetEnumerator();
        }




        public void CreateFrame(ref Normal n)
        {
            nn = n.ToVec().Normalize();
            Vector.CoordinateSystem(nn, out sn, out tn);
        }

        protected internal Vector tn, sn, nn;

        public Vector WorldToLocal(ref Vector v)
        {
            return new Vector(Vector.Dot(ref v, ref  sn), Vector.Dot(ref v, ref tn), Vector.Dot(ref v, ref nn));
        }
        public Vector LocalToWorld(Vector v)
        {
            return new Vector(sn.x * v.x + tn.x * v.y + nn.x * v.z,
                              sn.y * v.x + tn.y * v.y + nn.y * v.z,
                              sn.z * v.x + tn.z * v.y + nn.z * v.z);
        }
        public Vector LocalToWorld(ref Vector v)
        {
            return new Vector(sn.x * v.x + tn.x * v.y + nn.x * v.z,
                              sn.y * v.x + tn.y * v.y + nn.y * v.z,
                              sn.z * v.x + tn.z * v.y + nn.z * v.z);
        }



        public virtual RgbSpectrum Sample_f(ref Vector wo, out Vector wi, ref Normal N, ref Normal shadeN,
              ref RgbSpectrum in_fs, float u0, float u1, float u2, ref SurfaceTextureData surfaceData, out float pdf,
              out bool specularBounce)
        {
            BsdfEvent evt;
            var samp = this.Sample_f(ref wo, out wi, ref N, ref shadeN, ref in_fs, u0, u1, u2, ref surfaceData, out pdf, out evt);
            specularBounce = evt.Has(BsdfEvent.Specular);
            return samp;
        }
    }
}
